#include "./../includes/precedence.h"

/*********************************
 * 
 * Public methods
 * 
 * *******************************/

mathExpression::mathExpression( )
{
	this->expression = '0';

	this->precedences = new pilha<char>();

	this->parsedOperandis = new pilha<double>;
	this->parsedOpereitors = new pilha<char>;

	this->validExpression = true;

}

mathExpression::mathExpression( const char* _expr )
{	
	this->validExpression = true;

	this->precedences = new pilha<char>();

	this->parsedOperandis = new pilha<double>;
	this->parsedOpereitors = new pilha<char>;

	this->setExpr( _expr );
}
	
mathExpression::~mathExpression()
{
	
}
	
void
mathExpression::setExpr( const std::string _expr)
{
	if ( _expr != "")
	{
		this->expression = _expr;
		
		for( unsigned int counter = 0; counter < _expr.size(); counter++ )
		{
			if ( this->isPrecedenceOperator( _expr[ counter ] ) )
			{
				this->setPrecedenceOp( _expr[ counter ] );
			}
			if ( this->checkData( _expr[ counter ] ) )
			{
				this->parsedOpereitors->push( _expr[ counter ] );
			}
			else
			{
				int 
				auxCounter;

				for ( auxCounter = counter; ! this->checkData( _expr[ auxCounter ] ) ; ++auxCounter );
				
				this->parsedOperandis->push( atof( _expr.substr(counter, auxCounter - counter).c_str() ) );
				
				counter = auxCounter -1;
			}
		}
	}
	else
	{
		this->throwException( "NULL expression", 4 );
	}
}

bool
mathExpression::isValidExpression()
{
	if ( this->validExpression == false )
	{
		return false;
	}
	
	unsigned short int 
	contParentis = 0;
	
	unsigned short int
	contCochetis = 0;
	
	unsigned short int
	contChavis = 0;
	
	while(! this->precedences->isEmpty() )
	{
		switch( this->pauNoCu( this->precedences->getInfo() , contParentis , contCochetis , contChavis ) )
		{
		case openParentis:
			contParentis--;
			this->precedences->pop();
			break;
		case closeParentis:
			contParentis++;
			this->precedences->pop();
			break;
		case openCochetis:
			contCochetis--;
			this->precedences->pop();
			break;
		case closeCochetis:
			contCochetis++;
			this->precedences->pop();
			break;
		case openChavis:
			contChavis--;
			this->precedences->pop();
			break;
		case closeChavis:
			contChavis++;
			this->precedences->pop();
			break;
		default:
			return false;
			break;
		}
	}
	if ( contChavis == 0 && contCochetis == 0 && contParentis == 0 )
	{
		return true;
	}
	else
	{
		return false;
	}
}

std::string
mathExpression::getPosFix( )
{
	this->postFixedExpr = this->postFixatorPlus( this->expression );
	return this->postFixedExpr;
}

double
mathExpression::solveExpression()
{
	pilha<double>
	stackOperandis;

	for( unsigned int counter = 0 ; counter < this->postFixedExpr.length() ; counter++ )
	{
		if ( ! this->checkData( this->postFixedExpr[ counter ] ) )
		{
			int 
			auxCounter;

			for ( auxCounter = counter ;  this->postFixedExpr[ auxCounter ] != ' ' ; auxCounter++ );
			
			stackOperandis.push( atof( this->postFixedExpr.substr(counter, auxCounter - counter).c_str() ) );
			
			counter = auxCounter;
		}
		else
		{
			double
			secOperand = stackOperandis.getInfo();
			
			stackOperandis.pop();

			double
			firstOperand = stackOperandis.getInfo();
			
			stackOperandis.pop();
			
			switch ( this->postFixedExpr[ counter ] )
			{
			case TIMES:
				stackOperandis.push( firstOperand * secOperand );
				break;
			case DIV:
				stackOperandis.push( firstOperand / secOperand );
				break;
			case MINUS:
				stackOperandis.push( firstOperand - secOperand );
				break;
			case PLUS:
				stackOperandis.push( firstOperand + secOperand );
				break;
			default:
				this->throwException("pau no meio do seu cu!", 1);
				break;
			}
			
			counter++;
		}
	}
	
	return stackOperandis.getInfo();
}

/***********************************
 * 
 * 	Protected methods
 * 
 ***********************************/

void
mathExpression::setPrecedenceOp( const char _operator )
{
	if (this->precedences->isEmpty() )
	{
		if ( _operator != closeChavis || _operator != closeCochetis || _operator != closeParentis )
		{
			this->precedences->push( _operator );
		}
		else
		{
			this->validExpression = false;
		}
	}
	else
	{
		this->precedences->push( _operator );
	}
}


/*************************
 * 
 *  Private methods
 * 
 * ***********************/

void
mathExpression::throwException( std::string msg, const unsigned short int errorType )
{
	error*
	e = new error( msg );
	
	throw e;
}

std::string
mathExpression::postFixatorPlus( std::string _subExpression = " ")
{
	
	pilha<char>*
	operatorsStack = new pilha<char>;
	
	std::string
	postFixedExp;
	
	_subExpression = this->expression;
	
	for ( unsigned int counter = 0 ; counter < _subExpression.length() ; counter++ )
	{
		if( !this->checkData( _subExpression[ counter ] ) )
		{
			unsigned int 
			auxCounter;

			for ( auxCounter = counter; ! this->checkData( _subExpression[ auxCounter ] ) && auxCounter < _subExpression.length() ; ++auxCounter )
			{
				postFixedExp.push_back( _subExpression[ auxCounter ] );
			}
			
			postFixedExp.push_back( ' ' );

			counter = auxCounter -1;
		}
		else if( this->isOperator( _subExpression[ counter ] ) )
		{
			while( ! operatorsStack->isEmpty() && this->getPrecedenceValue( _subExpression[ counter ] ) < this->getPrecedenceValue( operatorsStack->getInfo() ) )
			{

				postFixedExp.push_back( operatorsStack->getInfo() );
				postFixedExp.push_back( ' ' );

				operatorsStack->pop();
			}
			operatorsStack->push( _subExpression[ counter ] );
		}
		else if( this->isPrecedenceOperator( _subExpression[ counter ] ) )
		{
			switch ( _subExpression[ counter ] )
			{
			case openParentis:
			case openCochetis:
			case openChavis:
				operatorsStack->push( _subExpression[ counter ] );
				break;
			case closeParentis:
			case closeCochetis:
			case closeChavis:
				char tmpOp = operatorsStack->getInfo();
				while ( tmpOp != openParentis && tmpOp != openCochetis && tmpOp != openChavis )
				{
					postFixedExp.push_back( tmpOp );
					postFixedExp.push_back( ' ' );

					operatorsStack->pop();
					tmpOp = operatorsStack->getInfo();
				}
				operatorsStack->pop();
			}
		}
	}
	
	while ( !operatorsStack->isEmpty() )
	{
		if( this->isPrecedenceOperator( operatorsStack->getInfo() ) )
		{
			this->throwException("oops... mismatched precedence operator" , 1);
		}
		else
		{
			postFixedExp.push_back( operatorsStack->getInfo() );
			operatorsStack->pop();
		}
	}
	
	return postFixedExp;
}

const bool
mathExpression::isPrecedenceOperator( const char _toCheck )
{
	switch ( _toCheck )
	{
	case openChavis:
	case openCochetis:
	case openParentis:
	case closeChavis:
	case closeCochetis:
	case closeParentis:
		return true;
		break;
	default:
		return false;
		break;
	}
}

const bool
mathExpression::isOperator( const char _toCheck )
{
	switch ( _toCheck )
	{
	case PLUS:
	case MINUS:
	case DIV:
	case TIMES:
	case POW:
		return true;
		break;
	default:
		return false;
		break;
	}
}

const unsigned short int
mathExpression::getPrecedenceValue( const char _toCheck )
{
	switch ( _toCheck )
	{
	case POW:
		return 3;
		break;
	case TIMES:
	case DIV:
		return 2;
		break;
	case PLUS:
	case MINUS:
		return 1;
		break;
	default:
		return 0;
		break;
	}
}

const unsigned short int
mathExpression::pauNoCu( const char& _operator					 ,
						 const unsigned short int _contParentis ,
						 const unsigned short int _contCochetis ,
						 const unsigned short int _contChavis	  )
{
	if ( _operator == closeChavis && _contParentis == 0 && _contCochetis == 0 )
	{
		return closeChavis;		
	}
	else if ( _operator == closeCochetis && _contParentis == 0 )
	{
		return closeCochetis;
	}
	else if ( _operator == closeParentis )
	{
		return closeParentis;
	}
	else if ( _operator == openParentis && _contParentis > 0 )
	{
		return openParentis;
	}
	else if ( _operator == openCochetis && _contCochetis > 0 && _contParentis == 0 )
	{
		return openCochetis;
	}
	else if ( _operator == openChavis && _contChavis > 0 && _contCochetis == 0 && _contParentis == 0 )
	{
		return openChavis;
	}
	else
	{
		return 42;
	}
}

const bool
mathExpression::checkData( const char _data )
{
	switch ( _data )
	{
	case openChavis:
	case openCochetis:
	case openParentis:
	case closeChavis:
	case closeCochetis:
	case closeParentis:
	case PLUS:
	case MINUS:
	case TIMES:
	case DIV:
	case POW:
		return true;
		break;
	default:
		return false;
		break;
	}
}
